"""
C++ dependency resolver.

Handles include resolution, namespace analysis, library dependencies,
and cross-compilation unit linking for C++, CUDA, and HIP code.
"""

import os
import re
from typing import Dict, List, Any, Optional, Set, Tuple
from pathlib import Path
from collections import defaultdict
import logging

from ...core.error_handler import AnalysisError, log_info, log_debug


class CppResolver:
    """C++ dependency and include resolver."""
    
    def __init__(self, config: Dict[str, Any] = None):
        """Initialize the C++ resolver."""
        self.config = config or {}
        
        # Standard include paths
        self.system_include_paths = self._get_system_include_paths()
        self.user_include_paths = []
        
        # Namespace tracking
        self.namespace_map = defaultdict(set)
        self.using_declarations = defaultdict(list)
        self.using_directives = defaultdict(list)
        
        # Library dependencies
        self.library_dependencies = set()
        self.framework_dependencies = set()
        
        # Include resolution cache
        self.include_cache = {}
        
        # GPU-specific includes
        self.cuda_include_paths = self._get_cuda_include_paths()
        self.hip_include_paths = self._get_hip_include_paths()
        
        log_debug("Initialized C++ dependency resolver")
    
    def resolve_includes(self, ast_data: Dict[str, Any], base_file_path: str = None) -> Dict[str, Any]:
        """
        Resolve all include dependencies.
        
        Args:
            ast_data: AST data containing include information
            base_file_path: Base file path for relative includes
            
        Returns:
            Resolved include dependency information
        """
        resolution_result = {
            'resolved_includes': [],
            'unresolved_includes': [],
            'system_includes': [],
            'local_includes': [],
            'gpu_includes': {
                'cuda': [],
                'hip': []
            },
            'include_tree': {},
            'circular_dependencies': [],
            'missing_includes': [],
            'include_statistics': {}
        }
        
        base_path = Path(base_file_path).parent if base_file_path else Path.cwd()
        
        for include in ast_data.get('includes', []):
            include_file = include['file']
            is_system = include.get('is_system_include', False)
            
            # Resolve the include
            resolved_path = self._resolve_single_include(include_file, base_path, is_system)
            
            if resolved_path:
                resolved_include = {
                    'original': include_file,
                    'resolved_path': str(resolved_path),
                    'is_system': is_system,
                    'exists': resolved_path.exists(),
                    'is_cuda': self._is_cuda_include(include_file),
                    'is_hip': self._is_hip_include(include_file),
                    'location': include.get('location', {})
                }
                
                resolution_result['resolved_includes'].append(resolved_include)
                
                if is_system:
                    resolution_result['system_includes'].append(resolved_include)
                else:
                    resolution_result['local_includes'].append(resolved_include)
                
                # Categorize GPU includes
                if resolved_include['is_cuda']:
                    resolution_result['gpu_includes']['cuda'].append(resolved_include)
                elif resolved_include['is_hip']:
                    resolution_result['gpu_includes']['hip'].append(resolved_include)
                
                # Track library dependencies
                self._track_library_dependency(include_file)
                
            else:
                unresolved_include = {
                    'original': include_file,
                    'is_system': is_system,
                    'location': include.get('location', {}),
                    'search_paths': self._get_search_paths(is_system)
                }
                resolution_result['unresolved_includes'].append(unresolved_include)
                resolution_result['missing_includes'].append(include_file)
        
        # Build include tree
        resolution_result['include_tree'] = self._build_include_tree(resolution_result['resolved_includes'])
        
        # Detect circular dependencies
        resolution_result['circular_dependencies'] = self._detect_circular_dependencies(resolution_result['include_tree'])
        
        # Generate statistics
        resolution_result['include_statistics'] = self._generate_include_statistics(resolution_result)
        
        return resolution_result
    
    def resolve_namespaces(self, ast_data: Dict[str, Any]) -> Dict[str, Any]:
        """
        Resolve namespace usage and dependencies.
        
        Args:
            ast_data: AST data containing namespace information
            
        Returns:
            Namespace resolution information
        """
        namespace_result = {
            'namespace_declarations': [],
            'using_declarations': [],
            'using_directives': [],
            'namespace_hierarchy': {},
            'unqualified_lookups': [],
            'namespace_conflicts': [],
            'anonymous_namespaces': []
        }
        
        # Process namespace declarations
        for namespace in ast_data.get('namespaces', []):
            ns_info = {
                'name': namespace['name'],
                'qualified_name': namespace['qualified_name'],
                'location': namespace['location'],
                'parent_namespace': namespace.get('parent_namespace'),
                'is_anonymous': namespace['name'] == '<anonymous>',
                'is_inline': namespace.get('is_inline', False)
            }
            
            namespace_result['namespace_declarations'].append(ns_info)
            
            if ns_info['is_anonymous']:
                namespace_result['anonymous_namespaces'].append(ns_info)
            
            # Build namespace map
            self.namespace_map[namespace['qualified_name']].update(
                self._get_namespace_contents(namespace, ast_data)
            )
        
        # Process using declarations
        for using_decl in ast_data.get('using_declarations', []):
            if using_decl['type'] == 'declaration':
                using_info = {
                    'name': using_decl['name'],
                    'target': using_decl.get('target_name'),
                    'location': using_decl['location'],
                    'scope': using_decl.get('scope', 'global')
                }
                namespace_result['using_declarations'].append(using_info)
                self.using_declarations[using_info['scope']].append(using_info)
            
            elif using_decl['type'] == 'directive':
                directive_info = {
                    'namespace': using_decl['namespace'],
                    'location': using_decl['location'],
                    'scope': using_decl.get('scope', 'global')
                }
                namespace_result['using_directives'].append(directive_info)
                self.using_directives[directive_info['scope']].append(directive_info)
        
        # Build namespace hierarchy
        namespace_result['namespace_hierarchy'] = self._build_namespace_hierarchy(namespace_result['namespace_declarations'])
        
        # Detect namespace conflicts
        namespace_result['namespace_conflicts'] = self._detect_namespace_conflicts(namespace_result)
        
        # Analyze unqualified lookups
        namespace_result['unqualified_lookups'] = self._analyze_unqualified_lookups(ast_data)
        
        return namespace_result
    
    def analyze_library_dependencies(self, resolved_includes: List[Dict[str, Any]]) -> Dict[str, Any]:
        """
        Analyze external library dependencies.
        
        Args:
            resolved_includes: List of resolved includes
            
        Returns:
            Library dependency analysis
        """
        dependency_result = {
            'standard_library': [],
            'third_party_libraries': [],
            'gpu_libraries': {
                'cuda': [],
                'hip': []
            },
            'framework_dependencies': list(self.framework_dependencies),
            'dependency_graph': {},
            'missing_libraries': [],
            'version_requirements': {}
        }
        
        for include in resolved_includes:
            include_file = include['original']
            
            # Categorize library type
            if self._is_standard_library(include_file):
                dependency_result['standard_library'].append(include_file)
            elif include['is_cuda']:
                cuda_lib = self._identify_cuda_library(include_file)
                if cuda_lib:
                    dependency_result['gpu_libraries']['cuda'].append(cuda_lib)
            elif include['is_hip']:
                hip_lib = self._identify_hip_library(include_file)
                if hip_lib:
                    dependency_result['gpu_libraries']['hip'].append(hip_lib)
            else:
                third_party = self._identify_third_party_library(include_file)
                if third_party:
                    dependency_result['third_party_libraries'].append(third_party)
        
        # Build dependency graph
        dependency_result['dependency_graph'] = self._build_dependency_graph(dependency_result)
        
        # Check for missing libraries
        dependency_result['missing_libraries'] = self._check_missing_libraries(dependency_result)
        
        # Analyze version requirements
        dependency_result['version_requirements'] = self._analyze_version_requirements(dependency_result)
        
        return dependency_result
    
    def _get_system_include_paths(self) -> List[Path]:
        """Get standard system include paths."""
        paths = []
        
        # Common system include paths
        common_paths = [
            '/usr/include',
            '/usr/local/include',
            '/usr/include/c++',
            '/opt/local/include'
        ]
        
        for path_str in common_paths:
            path = Path(path_str)
            if path.exists():
                paths.append(path)
        
        # Try to get compiler-specific paths
        compiler_paths = self._get_compiler_include_paths()
        paths.extend(compiler_paths)
        
        return paths
    
    def _get_compiler_include_paths(self) -> List[Path]:
        """Get compiler-specific include paths."""
        paths = []
        
        try:
            # Try to get paths from gcc/g++
            import subprocess
            result = subprocess.run(
                ['gcc', '-E', '-v', '-x', 'c++', '/dev/null'],
                capture_output=True,
                text=True,
                stderr=subprocess.STDOUT
            )
            
            # Parse the output to extract include paths
            in_include_section = False
            for line in result.stdout.split('\n'):
                if '#include <...> search starts here:' in line:
                    in_include_section = True
                    continue
                elif 'End of search list.' in line:
                    break
                elif in_include_section and line.strip():
                    path = Path(line.strip())
                    if path.exists():
                        paths.append(path)
        
        except Exception as e:
            log_debug(f"Could not get compiler include paths: {e}")
        
        return paths
    
    def _get_cuda_include_paths(self) -> List[Path]:
        """Get CUDA include paths."""
        paths = []
        
        # Common CUDA installation paths
        cuda_paths = [
            '/usr/local/cuda/include',
            '/opt/cuda/include',
            '/usr/include/cuda',
            os.path.expanduser('~/NVIDIA_GPU_Computing_SDK/C/common/inc')
        ]
        
        # Check CUDA_HOME environment variable
        cuda_home = os.environ.get('CUDA_HOME') or os.environ.get('CUDA_PATH')
        if cuda_home:
            cuda_paths.insert(0, os.path.join(cuda_home, 'include'))
        
        for path_str in cuda_paths:
            path = Path(path_str)
            if path.exists():
                paths.append(path)
        
        return paths
    
    def _get_hip_include_paths(self) -> List[Path]:
        """Get HIP include paths."""
        paths = []
        
        # Common HIP installation paths
        hip_paths = [
            '/opt/rocm/include',
            '/usr/include/hip',
            '/usr/local/hip/include'
        ]
        
        # Check ROCM_PATH environment variable
        rocm_home = os.environ.get('ROCM_PATH') or os.environ.get('HIP_PATH')
        if rocm_home:
            hip_paths.insert(0, os.path.join(rocm_home, 'include'))
        
        for path_str in hip_paths:
            path = Path(path_str)
            if path.exists():
                paths.append(path)
        
        return paths
    
    def _resolve_single_include(self, include_file: str, base_path: Path, is_system: bool) -> Optional[Path]:
        """Resolve a single include file."""
        # Check cache first
        cache_key = (include_file, str(base_path), is_system)
        if cache_key in self.include_cache:
            return self.include_cache[cache_key]
        
        resolved_path = None
        
        if is_system:
            # System include - search in system paths
            search_paths = self.system_include_paths + self.cuda_include_paths + self.hip_include_paths
            
            for search_path in search_paths:
                candidate = search_path / include_file
                if candidate.exists():
                    resolved_path = candidate
                    break
        else:
            # Local include - search relative to base path
            candidate = base_path / include_file
            if candidate.exists():
                resolved_path = candidate
            else:
                # Also search in user include paths
                for search_path in self.user_include_paths:
                    candidate = search_path / include_file
                    if candidate.exists():
                        resolved_path = candidate
                        break
        
        # Cache the result
        self.include_cache[cache_key] = resolved_path
        return resolved_path
    
    def _is_cuda_include(self, include_file: str) -> bool:
        """Check if include is a CUDA header."""
        cuda_patterns = [
            r'^cuda.*\.h$',
            r'^cub/',
            r'^thrust/',
            r'^cublas',
            r'^cufft',
            r'^curand',
            r'^cusparse',
            r'^cudnn',
            r'^npp',
            r'^cooperative_groups'
        ]
        
        return any(re.search(pattern, include_file.lower()) for pattern in cuda_patterns)
    
    def _is_hip_include(self, include_file: str) -> bool:
        """Check if include is a HIP header."""
        hip_patterns = [
            r'^hip/',
            r'^rocblas',
            r'^rocfft',
            r'^rocrand',
            r'^rocsparse',
            r'^miopen',
            r'^rccl'
        ]
        
        return any(re.search(pattern, include_file.lower()) for pattern in hip_patterns)
    
    def _get_search_paths(self, is_system: bool) -> List[str]:
        """Get search paths for include resolution."""
        if is_system:
            return [str(p) for p in self.system_include_paths + self.cuda_include_paths + self.hip_include_paths]
        else:
            return [str(p) for p in self.user_include_paths]
    
    def _track_library_dependency(self, include_file: str):
        """Track library dependencies based on includes."""
        # Map common includes to libraries
        library_map = {
            'iostream': 'std',
            'vector': 'std',
            'string': 'std',
            'algorithm': 'std',
            'memory': 'std',
            'cuda_runtime.h': 'cuda',
            'cublas_v2.h': 'cublas',
            'cufft.h': 'cufft',
            'hip/hip_runtime.h': 'hip',
            'rocblas.h': 'rocblas',
            'boost/': 'boost',
            'eigen3/': 'eigen',
            'opencv2/': 'opencv'
        }
        
        for pattern, library in library_map.items():
            if pattern in include_file.lower():
                self.library_dependencies.add(library)
                break
    
    def _build_include_tree(self, resolved_includes: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Build hierarchical include dependency tree."""
        tree = {
            'root': [],
            'dependencies': {},
            'reverse_dependencies': defaultdict(list)
        }
        
        for include in resolved_includes:
            include_path = include['resolved_path']
            tree['root'].append(include_path)
            
            # For a full implementation, we would recursively parse each included file
            # to build the complete dependency tree. For now, we'll create a simple structure.
            tree['dependencies'][include_path] = []
            
            # Track reverse dependencies
            tree['reverse_dependencies'][include['original']].append(include_path)
        
        return tree
    
    def _detect_circular_dependencies(self, include_tree: Dict[str, Any]) -> List[List[str]]:
        """Detect circular include dependencies."""
        circular_deps = []
        
        # For a complete implementation, we would need to perform a full dependency analysis
        # This would involve parsing all included files and checking for cycles.
        # For now, return empty list as placeholder.
        
        return circular_deps
    
    def _generate_include_statistics(self, resolution_result: Dict[str, Any]) -> Dict[str, Any]:
        """Generate include resolution statistics."""
        total_includes = len(resolution_result['resolved_includes']) + len(resolution_result['unresolved_includes'])
        
        return {
            'total_includes': total_includes,
            'resolved_includes': len(resolution_result['resolved_includes']),
            'unresolved_includes': len(resolution_result['unresolved_includes']),
            'system_includes': len(resolution_result['system_includes']),
            'local_includes': len(resolution_result['local_includes']),
            'cuda_includes': len(resolution_result['gpu_includes']['cuda']),
            'hip_includes': len(resolution_result['gpu_includes']['hip']),
            'resolution_rate': len(resolution_result['resolved_includes']) / total_includes if total_includes > 0 else 0.0
        }
    
    def _get_namespace_contents(self, namespace: Dict[str, Any], ast_data: Dict[str, Any]) -> Set[str]:
        """Get the contents of a namespace."""
        contents = set()
        
        namespace_name = namespace['qualified_name']
        
        # Find functions in this namespace
        for func in ast_data.get('functions', []):
            func_namespace = '::'.join(func.get('namespace', []))
            if func_namespace == namespace_name:
                contents.add(func['name'])
        
        # Find classes in this namespace
        for cls in ast_data.get('classes', []):
            cls_namespace = '::'.join(cls.get('namespace', []))
            if cls_namespace == namespace_name:
                contents.add(cls['name'])
        
        # Find variables in this namespace
        for var in ast_data.get('variables', []):
            var_namespace = '::'.join(var.get('namespace', []))
            if var_namespace == namespace_name:
                contents.add(var['name'])
        
        return contents
    
    def _build_namespace_hierarchy(self, namespace_declarations: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Build namespace hierarchy tree."""
        hierarchy = {}
        
        for ns in namespace_declarations:
            # Build nested structure based on qualified name
            parts = ns['qualified_name'].split('::')
            current = hierarchy
            
            for part in parts:
                if part not in current:
                    current[part] = {'children': {}, 'info': None}
                current = current[part]['children']
            
            # Set the namespace info at the leaf
            if parts:
                current_ns = hierarchy
                for part in parts[:-1]:
                    current_ns = current_ns[part]['children']
                current_ns[parts[-1]]['info'] = ns
        
        return hierarchy
    
    def _detect_namespace_conflicts(self, namespace_result: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect potential namespace conflicts."""
        conflicts = []
        
        # Check for using directive conflicts
        for scope, directives in self.using_directives.items():
            if len(directives) > 1:
                # Multiple using directives in same scope could cause conflicts
                directive_namespaces = [d['namespace'] for d in directives]
                if len(set(directive_namespaces)) > 1:  # Different namespaces
                    conflicts.append({
                        'type': 'using_directive_conflict',
                        'scope': scope,
                        'conflicting_namespaces': directive_namespaces,
                        'locations': [d['location'] for d in directives]
                    })
        
        return conflicts
    
    def _analyze_unqualified_lookups(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Analyze unqualified name lookups."""
        lookups = []
        
        # This would require detailed analysis of identifier usage
        # For now, return empty list as placeholder
        
        return lookups
    
    def _is_standard_library(self, include_file: str) -> bool:
        """Check if include is from C++ standard library."""
        std_headers = {
            'algorithm', 'array', 'atomic', 'bitset', 'chrono', 'codecvt',
            'complex', 'condition_variable', 'deque', 'exception', 'forward_list',
            'fstream', 'functional', 'future', 'initializer_list', 'iomanip',
            'ios', 'iosfwd', 'iostream', 'istream', 'iterator', 'limits',
            'list', 'locale', 'map', 'memory', 'mutex', 'new', 'numeric',
            'ostream', 'queue', 'random', 'ratio', 'regex', 'set', 'sstream',
            'stack', 'stdexcept', 'streambuf', 'string', 'system_error',
            'thread', 'tuple', 'type_traits', 'typeinfo', 'unordered_map',
            'unordered_set', 'utility', 'valarray', 'vector'
        }
        
        # Remove extension if present
        header_name = include_file.split('.')[0] if '.' in include_file else include_file
        
        return header_name in std_headers
    
    def _identify_cuda_library(self, include_file: str) -> Dict[str, str]:
        """Identify specific CUDA library from include."""
        cuda_libs = {
            'cuda_runtime.h': 'cuda_runtime',
            'cublas_v2.h': 'cublas',
            'cufft.h': 'cufft',
            'curand.h': 'curand',
            'cusparse.h': 'cusparse',
            'cudnn.h': 'cudnn',
            'npp.h': 'npp'
        }
        
        for pattern, lib_name in cuda_libs.items():
            if pattern in include_file:
                return {'name': lib_name, 'type': 'cuda', 'header': include_file}
        
        return {'name': 'cuda_unknown', 'type': 'cuda', 'header': include_file}
    
    def _identify_hip_library(self, include_file: str) -> Dict[str, str]:
        """Identify specific HIP library from include."""
        hip_libs = {
            'hip_runtime.h': 'hip_runtime',
            'rocblas.h': 'rocblas',
            'rocfft.h': 'rocfft',
            'rocrand.h': 'rocrand',
            'rocsparse.h': 'rocsparse'
        }
        
        for pattern, lib_name in hip_libs.items():
            if pattern in include_file:
                return {'name': lib_name, 'type': 'hip', 'header': include_file}
        
        return {'name': 'hip_unknown', 'type': 'hip', 'header': include_file}
    
    def _identify_third_party_library(self, include_file: str) -> Optional[Dict[str, str]]:
        """Identify third-party library from include."""
        third_party_patterns = {
            'boost/': 'boost',
            'eigen': 'eigen',
            'opencv': 'opencv',
            'Qt': 'qt',
            'gtk': 'gtk'
        }
        
        for pattern, lib_name in third_party_patterns.items():
            if pattern.lower() in include_file.lower():
                return {'name': lib_name, 'type': 'third_party', 'header': include_file}
        
        return None
    
    def _build_dependency_graph(self, dependency_result: Dict[str, Any]) -> Dict[str, List[str]]:
        """Build library dependency graph."""
        # This would require analyzing actual library dependencies
        # For now, return empty graph
        return {}
    
    def _check_missing_libraries(self, dependency_result: Dict[str, Any]) -> List[str]:
        """Check for missing library dependencies."""
        missing = []
        
        # Check if CUDA libraries are available when CUDA includes are used
        if dependency_result['gpu_libraries']['cuda']:
            if not self.cuda_include_paths:
                missing.append('CUDA toolkit not found')
        
        # Check if HIP libraries are available when HIP includes are used
        if dependency_result['gpu_libraries']['hip']:
            if not self.hip_include_paths:
                missing.append('HIP/ROCm toolkit not found')
        
        return missing
    
    def _analyze_version_requirements(self, dependency_result: Dict[str, Any]) -> Dict[str, str]:
        """Analyze version requirements for libraries."""
        requirements = {}
        
        # This would analyze specific headers for version requirements
        # For now, return basic requirements
        if dependency_result['gpu_libraries']['cuda']:
            requirements['cuda'] = '>= 10.0'
        
        if dependency_result['gpu_libraries']['hip']:
            requirements['hip'] = '>= 4.0'
        
        return requirements